/*
客户端管理器
负责创建和管理虚拟客户端，实现不同的压力测试模式
主要功能：
1. 客户端管理器创建和初始化
2. 瞬时发压模式 - 按QPS均匀分布请求
3. 普通发压模式 - 并发启动所有客户端
4. 客户端工厂函数
*/
package service

import (
	"context"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/go-redis/redis/v8"
	"github.com/google/uuid"
	"github.com/levigross/grequests"
	"gitlab.vrviu.com/inviu/backend-go-public/gopublic"
	"gitlab.vrviu.com/inviu/backend-go-public/gopublic/config"
	"gitlab.vrviu.com/inviu/backend-go-public/gopublic/rediswrap"
	"gitlab.vrviu.com/inviu/backend-go-public/gopublic/vlog"
)

// CreateAreaClientMgr 创建区域客户端管理器
// 用于批量创建和管理多个虚拟客户端
// 参数：
//
//	user_id_prefix: 用户ID前缀
//	start_uuid: 起始UUID
//	gid: 游戏ID
//	biz_type: 业务类型
//	uservo: 用户服务地址
//	uaccess: 访问服务地址
//	task_id: 任务ID
//	count: 客户端数量
//	areaType: 区域类型
//	holdtime: 保持时间
//	relaunch_duration: 重启间隔
//	wg: 同步等待组
//	controller: 客户端控制器
func CreateAreaClientMgr(user_id_prefix string, start_uuid, gid uint64, biz_type uint64, uservo, uaccess, task_id string, count, areaType, holdtime, relaunch_duration int, wg *sync.WaitGroup, controller *ClientController) *AreaClientMgr {

	// 创建客户端管理器实例
	clientMgr := &AreaClientMgr{
		AreaClients: make([]*AreaClient, count),                       // 预分配客户端切片
		Result_key:  fmt.Sprintf("%s_performance_temp_flow", task_id), // Redis结果存储键
	}

	// 循环创建指定数量的客户端
	for i := 0; i < count; i++ {
		var new_user_id string
		if controller.IsAutoRegister == false {
			// 使用递增UUID生成用户ID
			new_id := start_uuid + uint64(i)
			new_user_id = strconv.FormatUint(new_id, 10)

		} else {
			// 使用随机字符串生成用户ID
			new_user_id = fmt.Sprintf("%s-%s", user_id_prefix, gopublic.GenerateRandonString(12))
		}

		// 创建单个客户端实例
		clientMgr.AreaClients[i] = CreateAreaClient(0, gid, biz_type, new_user_id, uservo, uaccess, clientMgr.Result_key, areaType, holdtime, relaunch_duration, rediswrap.GetRedisCli(), wg, controller)

	}
	return clientMgr
}

// instantPressure 瞬时发压模式
// 按照指定的QPS（每秒请求数）均匀分布发送请求
// 使用定时器控制请求频率，避免瞬间流量冲击
// 参数：
//
//	request_rate: 每秒请求数（QPS）
func (clientMgr *AreaClientMgr) instantPressure(request_rate int) {
	// 计算请求间隔时间：1秒 / 请求数 = 单个请求间隔
	requestInterval := time.Second / time.Duration(request_rate)
	// 创建定时器，将request_rate个请求平均分布到1秒内
	tickCh := time.NewTicker(requestInterval)
	var err_msg string
	ctx := context.Background()

	// 遍历所有客户端，按定时器节拍启动
	for _, client := range clientMgr.AreaClients {
		// 获取任务控制器上下文，用于取消操作
		value, _ := ctx_map.Load(client.ClientController.TaskID)
		controller, _ := value.(*routine_controller)
		ctx_routine := controller.ctx

		errCh := make(chan error)
		select {
		case <-tickCh.C: // 等待定时器触发
			go func() {
				defer func() {
					if err := recover(); err != nil {
						vlog.Errorf("panic:%v", err)
					}
				}()
				// 启动客户端
				client.Start(errCh)
				if err, ok := <-errCh; ok {
					if err != nil {
						vlog.Errorf("[aclient] 用户[%s] [%s] client failed, err(%v)，client.status（%d）", client.UserID, client.FlowID, client.fail_msg, client.status)
						if client.fail_msg == nil {
							err_msg = "No Error"
						} else {
							err_msg = client.fail_msg.Error()
						}
						// 创建客户端信息并存储到Redis
						AreaClientInfo := CreateAreaClientInfo(client.Uid, client.Gid, client.bizType, client.AreaType, client.DevType, client.status, 1, client.Atoken, client.Utoken, client.DevID, client.FlowID, client.AreaAddr, client.UServoAddr, client.UAccessAddr, client.UserID, err_msg, client.AllocTime.Sub(client.PreAllocTime).Seconds())

						aci, err := json.Marshal(AreaClientInfo)
						if err != nil {
							fmt.Println("Error marshaling JSON:", err)

						}
						vlog.Infof("[aclient] [%d] [%s]AreaClientInfo(%v)", client.Uid, client.FlowID, AreaClientInfo)

						// 将结果存储到Redis哈希表中
						if _, err := client.redisCli.HSet(ctx, client.Result_key, client.UserID, aci).Result(); err != nil {
							vlog.Infof("save redis fail:(%v)", err)

						}
						return

					}

				}

			}()
		case <-ctx_routine.Done(): // 接收到取消信号
			client.wg.Done()
			vlog.Infof("[aclient] 用户[%s] [%s]强制退出！！！！！", client.UserID, client.FlowID)
			return

		}

	}

}

// normalPressure 普通发压模式
// 并发启动所有客户端，不控制启动频率
// 适用于测试系统的并发处理能力
func (clientMgr *AreaClientMgr) normalPressure() {
	var err_msg string
	ctx := context.Background()

	// 并发启动所有客户端
	for _, client := range clientMgr.AreaClients {

		errCh := make(chan error)
		client := client // 避免闭包问题
		go func() {
			// 启动客户端
			client.Start(errCh)
			if err, ok := <-errCh; ok {
				if err != nil {
					vlog.Errorf("[aclient] 用户[%s] [%s] start failed, err(%v)，client.status（%d）", client.UserID, client.FlowID, client.fail_msg, client.status)
					if client.fail_msg == nil {
						err_msg = "No Error"
					} else {
						err_msg = client.fail_msg.Error()
					}
					// 创建客户端信息
					AreaClientInfo := CreateAreaClientInfo(client.Uid, client.Gid, client.bizType, client.AreaType, client.DevType, client.status, 1, client.Atoken, client.Utoken, client.DevID, client.FlowID, client.AreaAddr, client.UServoAddr, client.UAccessAddr, client.UserID, err_msg, client.AllocTime.Sub(client.PreAllocTime).Seconds())

					// 序列化为JSON
					aci, err := json.Marshal(AreaClientInfo)
					if err != nil {
						vlog.Errorf("json marshal AreaClientInfo error(%v)", err)

					}
					vlog.Infof("[aclient] [%d] [%s]AreaClientInfo(%v)", client.Uid, client.FlowID, AreaClientInfo)
					// 存储到Redis
					if _, err := client.redisCli.HSet(ctx, client.Result_key, client.UserID, aci).Result(); err != nil {
						vlog.Errorf("save redis fail:(%v)", err)
					}

				}

			}

		}()

	}
}

// CreateAreaClientInfo 创建区域客户端信息
// 工厂函数，用于创建客户端状态信息结构体
// 参数包含客户端的所有状态信息，用于存储到Redis
func CreateAreaClientInfo(uid, Gid uint64, biz_type uint64, AreaType, DevType, status, is_done int, Atoken, Utoken, DevID, FlowID, AreaAddr, UServoAddr, UAccessAddr, UserID, fail_msg string, alloc_time float64) *AreaClientInfo {

	return &AreaClientInfo{
		Uid:         uid,         // 用户ID
		bizType:     biz_type,    // 业务类型
		AreaType:    AreaType,    // 区域类型
		Atoken:      Atoken,      // 区域Token
		Utoken:      Utoken,      // 用户Token
		DevID:       DevID,       // 设备ID
		Gid:         Gid,         // 游戏ID
		FlowID:      FlowID,      // 流程ID
		AreaAddr:    AreaAddr,    // 区域地址
		UServoAddr:  UServoAddr,  // 用户服务地址
		UAccessAddr: UAccessAddr, // 访问地址
		UserID:      UserID,      // 用户ID字符串
		DevType:     DevType,     // 设备类型
		Status:      status,      // 当前状态
		Fail_msg:    fail_msg,    // 失败消息
		Is_done:     is_done,     // 是否完成
		AllocTime:   alloc_time,  // 资源分配耗时
	}

}

// CreateAreaClient 创建区域客户端
// 客户端工厂函数，初始化所有必需的字段和依赖
// 参数：
//
//	uid: 用户ID
//	gid: 游戏ID
//	biz_type: 业务类型
//	userid: 用户ID字符串
//	uservo: 用户服务地址
//	uaccess: 访问服务地址
//	result_key: Redis结果键
//	areaType: 区域类型
//	holdtime: 保持时间
//	relaunch_duration: 重启间隔
//	redisCli: Redis客户端
//	wg: 同步等待组
//	controller: 客户端控制器
func CreateAreaClient(uid, gid uint64, biz_type uint64, userid, uservo, uaccess, result_key string, areaType, holdtime, relaunch_duration int, redisCli redis.UniversalClient, wg *sync.WaitGroup, controller *ClientController) *AreaClient {
	return &AreaClient{

		Uid:              uid,                                                        // 用户ID
		Gid:              gid,                                                        // 游戏ID
		bizType:          biz_type,                                                   // 业务类型
		DevID:            gopublic.GenerateRandonString(12),                          // 随机生成设备ID
		AreaType:         areaType,                                                   // 区域类型
		FlowID:           fmt.Sprintf("%d_%s", 0, gopublic.GenerateRandonString(12)), // 生成流程ID
		DevType:          5,                                                          // 设备类型（固定为5）
		UServoAddr:       uservo,                                                     // 用户服务地址
		UAccessAddr:      uaccess,                                                    // 访问服务地址
		UserID:           userid,                                                     // 用户ID字符串
		HBSigCh:          make(chan bool),                                            // 心跳信号通道
		StartTime:        time.Now(),                                                 // 开始时间
		HoldTime:         holdtime,                                                   // 保持时间
		status:           0,                                                          // 初始状态
		redisCli:         redisCli,                                                   // Redis客户端
		Result_key:       result_key,                                                 // 结果存储键
		RelaunchDuration: relaunch_duration,                                          // 重启间隔
		RelaunchTimes:    3,                                                          // 最大重启次数
		wg:               wg,                                                         // 同步等待组
		ClientController: controller,                                                 // 客户端控制器
	}
}

// Start 启动客户端
// 客户端的主要入口点，启动完整的生命周期流程
// 参数：
//
//	ch: 错误通道，用于返回启动过程中的错误
func (client *AreaClient) Start(ch chan error) {

	// 初始化时间戳
	client.StartTime = time.Now()
	client.QueueTime = client.StartTime
	client.PreAllocTime = client.StartTime

	// 获取任务控制器上下文
	value, _ := ctx_map.Load(client.ClientController.TaskID)
	controller, _ := value.(*routine_controller)
	ctx_routine := controller.ctx

	// 启动客户端主逻辑协程
	go func() {
		defer func() {
			if err := recover(); err != nil {
				vlog.Errorf("panic:%v", err)
			}
		}()
		if !client.ClientController.IsAutoResist {
			if err := client.applyUserToken(ctx_routine); err != nil {
				vlog.Errorf("[aclient] [%d] [%s] applyutoken failed: %v", client.Uid, client.FlowID, err)
				ch <- err
				client.wg.Done()
				close(ch)
				return
			}
		}

		// 根据IsTrylock参数决定是否调用trylock方法进行资源预锁定
		if client.ClientController.IsTrylock {
			if err := client.trylock(ctx_routine); err != nil {
				vlog.Errorf("[aclient] [%d] [%s] trylock failed: %v", client.Uid, client.FlowID, err)
				// trylock失败不影响后续流程，仅记录日志
			}
		}

		if err := client.unionApply(ctx_routine); err != nil {
			vlog.Errorf("[aclient] [%d] [%s] applyarea failed: %v", client.Uid, client.FlowID, err)
			ch <- err
			client.wg.Done()
			close(ch)
			return
		}

		client.wsConnect(ctx_routine, ch)
		client.launchGame(ctx_routine)

		// close(ch)

	}()

	return
}

// trylock 尝试锁定资源
// 基于云天API签名认证机制实现资源预锁定功能
// 使用HMAC-SHA1签名算法对请求进行身份验证
// 参数：
//
//	ctx: 上下文，用于取消操作
//
// 返回: error - 操作过程中的错误信息
func (client *AreaClient) trylock(ctx context.Context) error {
	// 获取配置中的访问密钥
	accessKey := config.GetConfigManager().Section("auth").Key("access_key").MustString("bd2365d1fdf816d1df806b358bf44de4")
	accessKeySecret := config.GetConfigManager().Section("auth").Key("access_key_secret").MustString("85e0bd4ade3a63ebbf92b00bbbcbe4e4")

	// 构建请求数据 - 从客户端获取业务类型和游戏ID
	requestData := map[string]interface{}{
		"session_id":     fmt.Sprintf("s%s", time.Now().Format("200601021504")), // 会话ID
		"app_biz_type":   client.bizType,                                        // 使用客户端的业务类型
		"app_userid":     client.UserID,                                         // 使用与CreateAreaClient相同的userid逻辑
		"gid":            strconv.Itoa(int(client.Gid)),                         // 使用客户端的游戏ID
		"dev_type":       5,                                                     // 设备类型
		"user_ip":        "",                                                    // 用户IP（可选）
		"lock_hold_time": 120,                                                   // 锁定保持时间（秒）
	}

	// 序列化请求数据为JSON
	dataJSON, err := json.Marshal(requestData)
	if err != nil {
		return fmt.Errorf("failed to marshal request data: %v", err)
	}
	dataStr := string(dataJSON)

	// 生成请求公共参数
	timestamp := strconv.FormatInt(time.Now().UnixMilli()+1000, 10) // 当前时间戳+1秒
	signatureNonce := uuid.New().String()                           // 随机签名随机数
	ticketNonce := uuid.New().String()                              // 随机票据随机数

	requestParams := map[string]string{
		"Format":           "JSON",
		"Version":          "2020-11-01", // 默认版本1.1对应的版本号
		"AccessKey":        accessKey,
		"Timestamp":        timestamp,
		"SignatureMethod":  "HMAC-SHA1",
		"SignatureVersion": "1.1", // 使用1.1版本
		"SignatureNonce":   signatureNonce,
		"Ticket":           "",
		"TicketNonce":      ticketNonce,
	}

	// 计算请求体MD5哈希值并进行Base64编码
	md5Hash := md5.Sum([]byte(dataStr))
	bodyMD5 := base64.StdEncoding.EncodeToString(md5Hash[:])

	// 构建标准化查询字符串
	var paramKeys []string
	for key := range requestParams {
		paramKeys = append(paramKeys, key)
	}
	sort.Strings(paramKeys) // 按字母顺序排序

	var encryptParts []string
	for _, key := range paramKeys {
		value := requestParams[key]
		encodedKey := url.QueryEscape(key)
		encodedValue := url.QueryEscape(value)
		encryptParts = append(encryptParts, fmt.Sprintf("%s=%s", encodedKey, encodedValue))
	}
	encryptString := strings.Join(encryptParts, "&")

	// 构建待签名字符串
	// 格式: HTTP方法&URI路径&Body MD5&标准化查询字符串
	targetURL := "https://api-bnd.yuntiancloud.com:18113/v3/servo/trylockresource"
	parsedURL, err := url.Parse(targetURL)
	if err != nil {
		return fmt.Errorf("failed to parse URL: %v", err)
	}

	signatureString := fmt.Sprintf("POST&%s&%s&%s",
		url.QueryEscape(parsedURL.Path),
		url.QueryEscape(bodyMD5),
		url.QueryEscape(encryptString))

	// 使用HMAC-SHA1计算签名
	secretKey := accessKeySecret + "&"
	mac := hmac.New(sha1.New, []byte(secretKey))
	mac.Write([]byte(signatureString))
	signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

	// 添加签名到请求参数
	requestParams["Signature"] = signature

	// 构建完整的URL（包含查询参数）
	baseURL, _ := url.Parse(targetURL)
	params := url.Values{}
	for key, value := range requestParams {
		params.Add(key, value)
	}
	baseURL.RawQuery = params.Encode()
	finalURL := baseURL.String()

	// 发送HTTP请求
	resp, err := grequests.Post(finalURL, &grequests.RequestOptions{
		RequestBody: strings.NewReader(dataStr),
		Headers: map[string]string{
			"Content-Type": "application/json",
		},
		RequestTimeout: time.Duration(config.GetConfigManager().Section("service").Key("apply_timeout").MustInt(6)) * time.Second,
	})

	if err != nil {
		vlog.Errorf("trylock request failed: %v", err)
		return fmt.Errorf("trylock request failed: %v", err)
	}

	if resp.StatusCode != 200 {
		vlog.Errorf("trylock failed with status code: %d, response: %s", resp.StatusCode, resp.String())
		return fmt.Errorf("trylock failed with status code: %d", resp.StatusCode)
	}

	vlog.Infof("trylock succeeded, response: %s", resp.String())
	return nil
}
